//media frame sync based on audio frame,so must have audio frame
//Decoder states.
const decoderStateIdle          = 0;
const decoderStateInitializing  = 1;
const decoderStateReady         = 2;
const decoderStateFinished      = 3;

//Player states.
const playerStateIdle           = 0;
const playerStatePlaying        = 1;
const playerStatePausing        = 2;
const playerStateStopped        = 3;


const eventRecordDownloadCompleted = 0x00000000;

//Constant.
const maxBufferTimeLength       = 1.0;
const downloadSpeedByteRateCoef = 2.0;


const VideoFrameLast            = -1;
const VideoFrameFirst           = 0;
const VideoFrameMiddle          = 1;

const isLittleEndian            = true;


/****frames come from websocket */
const FRAME_CONFIG              = 0x68643030;
const FRAME_IDR                 = 0x62643030;
const FRAME_PARTIAL             = 0x63643030;
const FRAME_AUDIO               = 0x62773030;
const FRAME_GPS                 = 0x73706730;
const FRAME_END                 = 0x481;

const MAX_VALUE_OF_INT         = 2147483647;

/*******start decoder.js  code */
const REQUEST_NEW_DECODER        = 0x6001;
const REQUEST_FEED_DECODER_DATA  = 0x6003;
const REQUEST_RELEASE_DECODEER   = 0x6004;
const REQUEST_FEED_GPS_DATA      = 0x6005;



/****Request */
const REQUEST_FETCH_START        = 0x6001;
const REQUEST_FETCH_RELEASE      = 0x6003;
const REQUEST_FETCH_FRAMES       = 0x6004;
const REQUEST_FETCH_PASSWORD     = 0x6005;
const REQUEST_FETCH_CACHE_SECONDS  = 0x6006;

/** Response */
const RESPONSE_FETCH_CLOSE       = 0x2002;
const RESPONSE_FETCH_ERROR       = 0x2003;
const RESPONSE_FETCH_PROGRESS    = 0x2004;
const RESPONSE_FETCH_WSERROR     = 0x2005;
const RESPONSE_FETCH_DONE        = 0x2006;
const RESPONSE_FETCH_READY       = 0x2007;
const REPONSE_SPEED_RATE         = 0x2008;
const RESPONSE_RECONNECTION      = 0x2009;

const RESPONSE_NEW_DECODER        = 0x6101;
const RESPONSE_GET_DECODED_DATA   = 0x6102;
const RESPONSE_FEED_DECODER_DATA  = 0x6103;
const RESPONSE_RELEASE_DECODEER   = 0x6104;
const RESPONSE_FLUSH_DECODER      = 0x6105;
const RESPONSE_SETUP_DECODER      = 0x6106;
/*******end decoder.js  code */

String.prototype.startWith = function(str) {
    var reg = new RegExp("^" + str);
    return reg.test(this);
};

function Player(option) {

    this.templateHTML       = `
                <div class="mejs-container mejs-video">
                    <div class="mejs-inner">
                    <div class="mejs-mediaelement">
                        <canvas class="rt-screen"></canvas>
                    </div>
                    <div class="mejs-layers">
                    <div class="mejs-overlay mejs-layer" style="display: none;">
                        <div class="mejs-overlay-loading"><span></span></div>
                    </div>
                    <div class="mejs-overlay mejs-layer" style="display: none;">
                        <div class="mejs-overlay-error">the Device is Busy right now</div>
                    </div>
                    <div class="mejs-overlay mejs-layer" style="display: none;">
                        <div class="mejs-overlay-info">info</div>
                    </div>
                    <div class="mejs-overlay mejs-layer mejs-overlay-play" style="display: none;">
                        <div class="mejs-overlay-button"></div>
                    </div>
                    </div>
                    <div class="mejs-top" style="display: none;z-index:9999">
                        <div class="deviceInfo mejs-clear" >
                            <div class="mejs-left-side" id="deviceInfo"></div>
                            <div class="mejs-right-side" id="deviceSpeed"></div>
                        </div>
                    </div>
                    <div class="mejs-controls" style="display: none;">
                    
                    <div class="mejs-button mejs-playpause-button mejs-pause mejs-2p">
                        <button type="button" aria-controls="mep_0" title="Play/Pause"></button>
                    </div>
                    <div class="mejs-button mejs-playpause-button mejs-stop">
                        <button type="button" aria-controls="mep_0" title="stop"></button>
                    </div>
                    <div class="mejs-button mejs-playpause-button mejs-jpeg">
                        <button type="button" aria-controls="mep_0" title="screenshot"></button>
                    </div>
                    <div class="mejs-time mejs-currenttime-container"><span class="mejs-currenttime">00:00:00</span></div>
                    <div class="mejs-time-rail" style="width: 486px;">
                        <span class="mejs-time-total" style="width: 476px;">
                        <span class="mejs-time-loaded" style="width: 0px;"></span>
                        <span class="mejs-time-current" style="width: 0px;"></span>
                        <span class="mejs-time-handle" style="left: 0px;"></span>
                        <span class="mejs-time-float" style="display: none; left: 244.963px;">
                            <span class="mejs-time-float-current">01:17</span>
                            <span class="mejs-time-float-corner"></span>
                        </span>
                    </span>
                    </div>
                    <div class="mejs-time mejs-duration-container">
                    <span class="mejs-duration">00:00:00</span>
                    </div>
                    <div class="mejs-button mejs-volume-button mejs-mute">
                    <button type="button" aria-controls="mep_0" title="Mute Toggle"></button>
                    <div class="mejs-volume-slider" style="display: none;">
                        <div class="mejs-volume-total"></div>
                        <div class="mejs-volume-current" style="height: 100px; top: 8px;"></div>
                        <div class="mejs-volume-handle" style="top: 100px;"></div>
                    </div>
                    </div>
                    <div class="mejs-button mejs-fullscreen-button">
                    <button type="button" aria-controls="mep_0" title="Fullscreen"></button>
                    </div>
                </div>
                <div class="mejs-clear"></div></div></div>
                </div>`;

    this.pixFmt             = 0;
    this.videoWidth         = 0;
    this.videoHeight        = 0;
    this.yLength            = 0;
    this.uvLength           = 0;


    //this.logger             = new Logger("Player");

    //fetch video/audio through websocket
    this.websocket          = null;
    //the url of websocket
    this.url                = "";
    //Get GPS data from live stream

    //Get GPS data from history record
    //this.pastGPSCallback    = null;
    //websocket send playParams to play video/audio
    this.playParams         = null;

    this.playerState        = playerStateStopped;
    //playing audio/video
    this.pcmPlayer          = null;
    this.canvas             = null;
    this.webglPlayer        = null;
    //it is on live
    this.isLive             = false;
    //save history records into a local file
    this.localFile          = null; // is not live ,save raw frames in MEMFS
    this.localQueue      = []; // is live ,save raw frames in QUEUE
    this.readOffset         = 0;
    this.writeOffset        = 0;


    /*** audio config ***/
    this.audioEncoding      = "16bitInt";
    this.audioChannels      = 1;
    this.audioSampleRate    = 8000;
    this.audioType          = 1; //1:G711a 2:AAC
    /*** video config ***/
    this.videoWidth         = 0;
    this.videoheight        = 0;
    this.videoFrameRate     = 30;
    this.videoType          = 1; //1:H264 2:H265
    //decode frame Buffer
    this.videoFrameBuffer   = [];
    this.audioFrameBuffer   = [];
    this.GpsFrameBuffer     = [];
    //loop timestamp in milliseconds,loopTotalTime refered to how many millseconds has pass away
    this.loopNowTime        = 0;
    this.loopLastTime       = 0;
    this.loopTotalTime      = 0;
    this.cacheTotolTime     = 0;

    //tracker timer
    this.requestDecodingTimer       = null;
    this.duration           = 0;
    this.trackerTimer       = null;
    this.timeLabel          = null;
    this.durationLabel      = null;
    this.timeTrack          = null;
    this.timeCacheTrack     = null;

    //if the frames of videoFrameBuffer is not enough to play for 1 seconds(1000ms),will request for decoding
    this.minVideoBufferDuration = 1000;

    /***decode worker */
    this.decodeWorker       = null;
    this.isFirstVideoFrame  = true;
    this.isFirstAudioFrame  = true;
    //customize pts begin from 0;
    this.beginTimeOffset    = -1;
    this.url = "";
    //loopRender timer
    this.loopRenderTask     = null;

    //playParams
    this.deviceID           = null;
    this.channel            = null;
    this.sessionKey         = null;
    this.startOffset        = null;
    this.startTime          = null;
    this.endTime            = null;
    this.GPSCallback        = null;

    //0:paused 1:playing
    this.cacheQueuePlayState   =  0;
    this.cacheMillisToStarted  = 0;
    this.cacheMillisToPlaying  = 0;
    this.cacheQueue             = [];

    //encrypted stream
    this.onRequestPassword  = null;
    this.isEncryptedStream  = false;
    this.passwordMd5        = "";

    //object onPlayStart(),onPlayStop();
    this.IOnBtnStopEvent    = null;
    this.fetchWorker        = null;

    this.gainValue          = 0.0;

    this.connectionRetryTimes = 3;
    this.connectionRetryDuration = 3000;
    this.onStoppEventHandler = null;
    this.cacheSeconds = 0;

    //media streaming may drop some video/audio frame when network is bad,
    //so pts has very big gap with lastPts
    this.lastAudioPtsAdjust   = -1;
    this.lastVideoPtsAdjust   = -1;
    this.baseAudioPtsAdjust   = -1;
    this.baseVideoPtsAdjust   = -1;

    console.info("option",option);
    if(option!=null){
        for(var key in option){
            this[key] = option[key];
        }
    }
    this._initPlayer();
    this.setVolume(this.gainValue);
}

var _setOnStoppEvent = function(callback){
    this.onStoppEventHandler = callback;
}

var _setCacheSeconds = function(){
    this.cacheMillisToStarted  = parseInt(this.cacheSeconds * 1000);
    this.cacheMillisToPlaying  = parseInt(this.cacheSeconds * 1000 * 0.7)
    console.info("cacheSeconds="+this.cacheSeconds,"cacheMillisToStarted="+this.cacheMillisToStarted,"cacheMillisToPlaying="+this.cacheMillisToPlaying);
    //this.cacheQueue  = [];
}

var _addCacheQueue = function(mediaObject){
    this.cacheQueue.push(mediaObject);
    const size = this.cacheQueue.length;
    const isLastMediaFrame = mediaObject.index == -1;
    ///
    if(size >= 1){
        if(isLastMediaFrame){
            this.cacheQueuePlayState = 1;
        }else if(this.cacheQueuePlayState == 0){
        var firstFrame = null;
        const lastFrame = mediaObject;
        for(var i=0;i< size ;i++){
            if(this.cacheQueue[i].isAudio == lastFrame.isAudio){
                firstFrame = this.cacheQueue[i];
                break;
            }
        }
        if(firstFrame == null) return;
        //console.info("_adCacheQueue",lastFrame.pts , firstFrame.pts,this.cacheMillisToStarted)
        if(lastFrame.pts - firstFrame.pts >= this.cacheMillisToStarted ||
            this.cacheQueue.length > 90){
            this.cacheQueuePlayState = 1;
            console.info("this.cacheQueuePlayState to playing","1="+lastFrame.pts,"0="+firstFrame.pts,this.cacheMillisToStarted);
        }
       }
    }
}

var _adjustLoopTotalTime = function(mediaObject) {
    var diff1 = 0;
    const pts = mediaObject.pts;
    if(mediaObject.isAudio){
        if(this.baseAudioPtsAdjust == -1)
            this.baseAudioPtsAdjust = pts;
        if(this.lastAudioPtsAdjust == -1)
            this.lastAudioPtsAdjust = pts;
        diff1 = pts - this.lastAudioPtsAdjust;
        if(diff1 > 600){
            console.info("audio jmp from DVR");
            mediaObject.pts = Number(this.lastAudioPtsAdjust - this.baseAudioPtsAdjust + 600);
            //jump from DVR
        }else if(diff1 < 0){
             console.info("audio jmp from bodycamera,can not happened,diff1="+diff1,"pts="+pts,"lastPts="+this.lastAudioPtsAdjust);
             mediaObject.pts = this.lastVideoPtsAdjust - this.baseVideoPtsAdjust + 30;
             this.lastVideoPtsAdjust += 30;
             return;
            //jmp from bodycamera
        }else{
            mediaObject.pts = pts - this.baseAudioPtsAdjust;
        }
        this.lastAudioPtsAdjust = pts;
    }else{
        if(this.baseVideoPtsAdjust == -1)
            this.baseVideoPtsAdjust = pts;
        if(this.lastVideoPtsAdjust == -1)
            this.lastVideoPtsAdjust = pts;
        diff1 = pts - this.lastVideoPtsAdjust;
        if(diff1 > 600){
            console.info("video jmp from DVR");
            mediaObject.pts = this.lastVideoPtsAdjust - this.baseVideoPtsAdjust + 600;
            //jump from DVR
        }else if(diff1 < 0){
            console.info("video jmp from bodycamera,can not happened,diff1="+diff1,"pts="+pts,"lastPts="+this.lastVideoPtsAdjust);
             //mediaObject.pts = this.lastVideoPtsAdjust - this.baseVideoPtsAdjust + 30;
             mediaObject.pts = this.lastVideoPtsAdjust - this.baseVideoPtsAdjust + 30;
             this.lastVideoPtsAdjust += 30;
             return;
            //jmp from bodycamera
        }else{
            mediaObject.pts -= this.baseVideoPtsAdjust;
        }
        this.lastVideoPtsAdjust = pts;
    }
}

var _popCacheQueue = function(){
    var mediaObject = null
    var size = 0;
    if(this.cacheQueuePlayState == 1){
        mediaObject = this.cacheQueue[0];
        this._adjustLoopTotalTime(mediaObject);
        console.info("_popCacheQueue",mediaObject.pts,"<",this.loopTotalTime + 150,"videoBufferSize="+this.videoFrameBuffer.length,"audioBufferSize="+this.audioFrameBuffer.length);
        if(mediaObject.pts > this.loopTotalTime + 150 && 
            (this.videoFrameBuffer.length > 0 ||
            this.audioFrameBuffer.length > 0)){
            return null;
        }
        this.cacheQueue.shift();
        //lastMediaFrame
        if(mediaObject.index == -1) return mediaObject;
        size = this.cacheQueue.length;
        if(size <= 0){
            this.cacheQueuePlayState = 0;
        }
    }
    return mediaObject;
}

var _resetMediaObjectCache = function(){
    this.cacheQueuePlayState = 0;
    this.cacheQueue = [];
    this.lastAudioPtsAdjust   = -1;
    this.lastVideoPtsAdjust   = -1;
    this.baseAudioPtsAdjust   = -1;
    this.baseVideoPtsAdjust   = -1;
    this.videoFrameBuffer = [];
    this.audioFrameBuffer = [];
    this.loopTotalTime = 0;
}

var _requestStreamFailed = function(message,duration){
    this.stop(true);
    this._loadErrorUI(message,true);
    var self = this;
    setTimeout(function(){
        self._hideOverlay();
    },duration);
}



var _initPlayer = function(){
   var rootElement = this.container;
   var self = this;
   //console.info(rootElement);
   if(rootElement == null) return ;
   rootElement = $(rootElement);
   rootElement.html(this.templateHTML);
   rootElement.mouseover(function(){
      $(this).find(".mejs-controls").show();
      $(this).find(".mejs-top").show();

   });
   rootElement.mouseout(function(){
     $(this).find(".mejs-controls").hide();
     $(this).find(".mejs-top").hide();
   });
   this.timeLabel = rootElement.find(".mejs-currenttime");
   this.durationLabel = rootElement.find(".mejs-duration");
   this.timeTrack = rootElement.find(".mejs-time-current");
   this.timeCacheTrack = rootElement.find(".mejs-time-loaded");

   this.canvas = rootElement.find(".rt-screen");
   this.playButton = rootElement.find(".mejs-2p");
   this.stopButton = rootElement.find(".mejs-stop");
   rootElement.find(".mejs-fullscreen-button>button").click(function(){
        self.fullscreen();
   });
   this.jpegButton = rootElement.find(".mejs-jpeg");
   this.jpegButton.click(function(){
        self.screenshot();
   });

   this.playButton.click(function(){
        if(self.isPlaying()){
            self.pause();
        }else if(self.isPaused()){
            self.resume();
        }
   });

   this.stopButton.click(function(){
        self.stop(false);
   })

   rootElement.find(".mejs-overlay-button").click(function(){
        self.resume();
   });

   var timeRange = rootElement.find(".mejs-time-total");
   timeRange.click(function(event){
        if(self.isLive || self.duration < 3) return;
        var ele = $(this);
        var width = ele.width();
        var offset = ele.offset();
        var x = event.pageX - offset.left -3;
        if(x > width) x = width;
        var seconds = self.duration * parseFloat(x/ width) / 1000;
        console.info("seek seconds=%d",seconds)
        self.seekTo(seconds);
   });
   timeRange.mousemove(function(event){
        if(self.isLive || self.duration < 3) return;
        var ele = $(this);
        var width = ele.width();
        var timeHandle = ele.find(".mejs-time-handle");
        var timeFloat = ele.find(".mejs-time-float");
        var offset = ele.offset();
        var x = event.pageX - offset.left -3;
        if(x > width) x = width;
        timeHandle.css({
            left:x,
            display:"block",
        });
        timeFloat.css({
            display:"block",
            left:x
        });
        timeFloat.find(".mejs-time-float-current").html(formatTime(self.duration * parseFloat(x/ width)));
   });

   timeRange.mouseout(function(){
        if(self.isLive) return ;
        var ele = $(this);
        var timeHandle = ele.find(".mejs-time-handle");
        var timeFloat = ele.find(".mejs-time-float");
        timeHandle.hide();
        timeFloat.hide();
   });

   //adjust volume
   rootElement.find(".mejs-volume-slider").mouseover(function(event){
        $(this).show();
    }).mouseout(function(event){
        $(this).hide();
   });

   rootElement.find(".mejs-volume-slider").click(function(event){
       isClick = false
        var ele = $(this);
        const totalHeight = ele.find(".mejs-volume-total").height();
       // console.info("++++++this",ele,totalHeight)
        var offset = ele.offset();
        var y = event.pageY - offset.top;
        if(y > totalHeight) y = totalHeight;
        var tmpTop = y;
        // console.info("slider move",event,tmpTop,parseFloat( (totalHeight - tmpTop) / 100),totalHeight);
        ele.find(".mejs-volume-handle").css(
            "top",tmpTop
        );
        self.setVolume(parseFloat( (totalHeight - tmpTop) / 100));
        // console.info("volume click");
   })

    var isClick = false
    rootElement.find(".mejs-volume-handle").mousedown(function (event){
        isClick = true
        console.info("slider down",event)
    })

    rootElement.find(".mejs-volume-slider").mousemove(function (event){
        var ele =rootElement.find(".mejs-volume-slider");
        if (isClick){
            const totalHeight = ele.find(".mejs-volume-total").height();
            // console.info("++++++",ele,totalHeight)
            var offset = ele.offset();
            var y = event.pageY - offset.top;
            if(y > totalHeight) y = totalHeight;
            var tmpTop = y;
            // console.info("slider move",event,tmpTop,totalHeight,parseFloat( (totalHeight - tmpTop) / 100),totalHeight);
            ele.find(".mejs-volume-handle").css(
                "top",tmpTop
            );
            self.setVolume(parseFloat( (totalHeight - tmpTop) / 100));
            // console.info("slider move",event)
        }

    })

    rootElement.find(".mejs-container").mouseup(function (event){
        isClick = false
        console.info("slider mouseup",rootElement,event)
    })

    rootElement.find(".mejs-volume-slider").mouseup(function (event){
        isClick = false
        console.info("slider mouseup",rootElement,event)
    })
    rootElement.find(".mejs-volume-slider").mouseleave(function (event){
        isClick = false
        console.info("slider mouseleave",rootElement,event)
    })

    rootElement.find(".mejs-volume-button").click(function(){
        var ele = rootElement.find(".mejs-volume-slider");
        !ele.is(':visible')  && ele.show();
    });



   rootElement.find(".mejs-volume-slider").click(function(event){
        var ele = $(this);
        const totalHeight = ele.find(".mejs-volume-total").height();
        var offset = ele.offset();
        var y = event.pageY - offset.top;
        if(y > totalHeight) y = totalHeight;
        var tmpTop = y;
        console.info("slider move",tmpTop,parseFloat( (totalHeight - tmpTop) / 100),totalHeight);
        ele.find(".mejs-volume-handle").css(
            "top",tmpTop
        );
        self.setVolume(parseFloat( (totalHeight - tmpTop) / 100));
        console.info("volume click");
   })


    rootElement.find(".mejs-volume-button").click(function(){
        var ele = rootElement.find(".mejs-volume-slider");
        !ele.is(':visible')  && ele.show();
    });



   //mejs-time-total
   //mejs-time-rail
   //this.timeCacheTrack =
   this.resize(this.width,this.height);
   console.info("_initPlayer")
}

var _loadingUI = function(isShow){
    var rootElement = this.container;
    rootElement = $(rootElement);
    if(isShow){
        rootElement.find(".mejs-overlay").hide();
        rootElement.find(".mejs-overlay-loading").parent(".mejs-layer").show();
    }else{
        rootElement.find(".mejs-overlay-loading").parent(".mejs-layer").hide();
    }
}
var _loadErrorUI = function(message,isShow){
    var rootElement = this.container;
    rootElement = $(rootElement);
    if(isShow){
        rootElement.find(".mejs-overlay").hide();
        rootElement.find(".mejs-overlay-error").html(message).parent(".mejs-layer").show();
    }else{
        rootElement.find(".mejs-overlay-error").html("").parent(".mejs-layer").hide();
    }
}


var _screenshot = function(){
    console.log(this.canvas,this.canvas.get(0));
    const dataUrl = this.canvas.get(0).toDataURL("image/jpeg", 0.93);
    const filename = getJpegName();
    // IE-specific download (msSaveBlob)
  if (window.navigator.msSaveBlob) {
    const byteString = atob(dataUrl.split(',')[1]);
    const mimeString = dataUrl.split(',')[0].split(':')[1].split(';')[0];
    const ab = new ArrayBuffer(byteString.length);
    const ia = new Uint8Array(ab);
    for (let i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }
    const blob = new Blob([ab], { type: mimeString });
    window.navigator.msSaveBlob(blob, filename);
  } else {
    // Standard browsers
    const link = document.createElement("a");
    link.href = dataUrl;
    link.download = filename;
    link.click();
  }
}

var _hideOverlay = function(){
    var rootElement = this.container;
    rootElement = $(rootElement);
    rootElement.find(".mejs-overlay").hide();
}

var _loadingInfoUI = function(message,isShow){
    var rootElement = this.container;
    rootElement = $(rootElement);
    var self = this;
    if(isShow){
        rootElement.find(".mejs-overlay").hide();
        rootElement.find(".mejs-overlay-info").html(message).parent(".mejs-layer").show();
    }else{
        rootElement.find(".mejs-overlay-info").html("").parent(".mejs-layer").hide();
    }
}

var _loadPlayPauseUI = function(isPlay){
    var rootElement = this.container;
    rootElement = $(rootElement);
    if(isPlay){
        rootElement.find(".mejs-overlay").hide();
        rootElement.find(".mejs-2p").removeClass("mejs-play").addClass("mejs-pause");
    }else{
        rootElement.find(".mejs-2p").removeClass("mejs-pause").addClass("mejs-play");
        //rootElement.find(".mejs-overlay").hide();
        //rootElement.find(".mejs-overlay-button").parent(".mejs-overlay").show();
    }
}


var _resizeUI = function(width,height){
    var rootElement = this.container;
    rootElement = $(rootElement);
    this.width = parseInt(width);
    this.height = parseInt(height);
    //rootElement.width(this.width).height(this.height);
    rootElement.find(".mejs-container").width(this.width).height(this.height);
    this.canvas.attr({"width":this.width,"height":this.height});
    rootElement.find(".mejs-overlay").width(this.width).height(this.height);
    var trackWidth =  this.width - (this.isLive ? 226 : 252);
    rootElement.find(".mejs-time-rail").width(trackWidth);
    rootElement.find(".mejs-time-total").width(trackWidth - 10);
    this.webglPlayer && this.webglPlayer.invalidCanvasAdjustment();
}

var _getCacheSeconds = function(){
    return this.cacheSeconds;
}


var _renderAudioFrame = function (data) {
    if(!this.pcmPlayer){
        this.pcmPlayer = new PCMPlayer({
            encoding: this.audioEncoding,
            channels: this.audioChannels,
            sampleRate: this.audioSampleRate,
            flushingTime: 5000
        });
        this.pcmPlayer.setVolume(this.gainValue);
    }
    this.pcmPlayer.play(new Uint8Array(data.data));
};

var _renderVideoFrame = function(obj) {
    var data = new Uint8Array(obj.data);
    var width = obj.width;
    var height = obj.height;
    var yLength = width * height;
    var uvLength = (width / 2) * (height / 2);

    if(!this.webglPlayer) {
        this.webglPlayer = new WebGLPlayer(this.canvas.get(0), {
            preserveDrawingBuffer: false
        });
    }
    this.webglPlayer.renderFrame(data, width, height, yLength, uvLength);
    //console.info("_renderVideoFrame");
}

var _setSpeedRate = function(speedRate){
    var rootElement = this.container;
    rootElement = $(rootElement);
    rootElement.find("#deviceSpeed").html(speedRate);
}

var _startTracker = function(){
    var self = this;
    if(self.trackerTimer == null){
        self.trackerTimer = setInterval(function(){
            self._updateTracker();
        },1000);
    }
    this.timeTrack && this.timeTrack.width(0);
    this.timeCacheTrack && this.timeCacheTrack.width(0);
    if(this.isLive){
        this.durationLabel && this.durationLabel.html("23:59:59");
        this.timeLabel && this.timeLabel.html("00:00:00");
        if(self.requestDecodingTimer == null){
            self.requestDecodingTimer = setInterval(function(){
               self._requestDecoding();
            },20);
        }
    }else{
        if(this.duration > 0){
            if(self.requestDecodingTimer == null){
                self.requestDecodingTimer = setInterval(function(){
                   self._requestDecoding();
                },20);
            }
            this.durationLabel.html(formatTime(this.duration));
            this.timeLabel.html("00:00:00");
        }
    }
}



var _updateTracker = function(){
   // console.info("_updateTracker",!this.trackerTimer,this.loopTotalTime);
    if(!this.trackerTimer) return ;
    if(this.isStopped()) return ;

    if(this.isLive){
        if(this.timeLabel){
           this.timeLabel.html(formatTime(this.loopTotalTime));
        }
    }else{
        if(this.isPlaying()){
            if(this.timeLabel){
                this.timeLabel.html(formatTime(this.loopTotalTime));
            }
            var rate = 0;
            var rootElement = $(this.container);
            var totalWidth = rootElement.find(".mejs-time-total").width();
            if(this.timeTrack){
                rate = (this.loopTotalTime / this.duration).toFixed(2);
                rate = rate > 1 ? 1 : rate;
                this.timeTrack.width(totalWidth * rate);
            }
            if(this.timeCacheTrack){
                rate = (this.cacheTotolTime / this.duration).toFixed(2);
                rate = rate > 1 ? 1 : rate;
                this.timeCacheTrack.width(totalWidth * rate);
            }
        }else if(this.isPaused()){
            if(this.timeCacheTrack){
                var rate = 0;
                var rootElement = $(this.container);
                var totalWidth = rootElement.find(".mejs-time-total").width();
                rate = (this.cacheTotolTime / this.duration).toFixed(2);
                rate = rate > 1 ? 1 : rate;
                this.timeCacheTrack.width(totalWidth * rate);
            }
        }

    }
}

var _stopTracker = function(){
    if(this.trackerTimer != null){
        clearInterval(this.trackerTimer)
        this.trackerTimer = null;
        if(this.timeLabel){
           this.timeLabel.html("");
        }
        if(this.durationLabel){
            this.durationLabel.html("");
        }
        if(this.timeTrack){
            this.timeTrack.width(0);
        }
        //set ui to original position
    }
    if(this.requestDecodingTimer !=null){
        clearInterval(this.requestDecodingTimer);
        this.requestDecodingTimer = null;
    }
}

/**
 *
 * @params data  {videoType:1,
 * {audioType:1,sampleRate:8000,channelCount: 1}
 */
var _initDecoder = function(){
    var self = this;
    if(this.decodeWorker != null) return ;
    this.decodeWorker = new Worker("static/video/decoder.js");
    this.decodeWorker.onmessage = function (evt) {
        var data = evt.data;
        switch (data.type) {
            case RESPONSE_NEW_DECODER:
                self._onInitNewDecoder(data);
                break;
            case RESPONSE_FEED_DECODER_DATA:
                self._onFeedDataHasErorr(data);
                break;
            case RESPONSE_RELEASE_DECODEER:
                self._onDecodeFinished(data);
                break;
            case RESPONSE_GET_DECODED_DATA:
                self._onReceiveDecodeFrame(data);
                break;
            case RESPONSE_SETUP_DECODER:
                console.info("decoderWorker ready to work")
                self._initFetch();
                //self._startWebsocket();
                break;
        }
    }
    console.info("new Worker - decoder.js");
}

var _initFetch = function(){
    var self = this;
    if(this.fetchWorker != null) return ;
    this.fetchWorker = new Worker("static/video/fetch.js");
    this.fetchWorker.onmessage = function (evt) {
        var data = evt.data;
        switch (data.type) {
            case REQUEST_NEW_DECODER:
                self._resetMediaObjectCache();
                self._startTracker();
                self.isFirstVideoFrame = true;
                self.cacheTotolTime = 0;
                self.isEncryptedStream = data.isEncryptedStream;
                if(self.isEncryptedStream){
                    self.passwordMd5 = data.passwordMd5;
                    self.onRequestPassword!=null && self.onRequestPassword();
                    self._loadingInfoUI("please input correct password...",true);
                }else{
                    self._loadingInfoUI("The device is sending data, please wait...",true);
                }
                self.audioType = data.audioType;
                self.videoType = data.videoType;
                self.audioSampleRate = data.sampleRate;
                self.channelCount = data.channelCount;
                self.decodeWorker.postMessage(data);
                break;
            case REQUEST_FEED_DECODER_DATA:
                if(!data.isAudio){
                    if(self.isFirstVideoFrame){
                       self.isFirstVideoFrame = false;
                       self._loadingInfoUI(null,false);
                    }
                }
                //if(!data.isAudio)testFile(data.data);
                self._addCacheQueue(data);
                /*if(self.isLive){
                    self.decodeWorker.postMessage(data,[data.data]);
                }else{
                    self.localQueue.push(data);
                    //if(!data.isAudio)testFile(data.data)
                }*/
                break;
            case REQUEST_FEED_GPS_DATA:
                self.GpsFrameBuffer.push(data);
                break;
            case RESPONSE_FETCH_PROGRESS:
                self.cacheTotolTime = data.pts;
                break;
            case RESPONSE_FETCH_CLOSE:
                //self.isIdle() && self.stop();
                self.stop(true);
                break;
            case REPONSE_SPEED_RATE:
                self._setSpeedRate(data["speedRate"]);
                break;    
            case RESPONSE_FETCH_WSERROR:
                self._requestStreamFailed("network error,please check network status",6800);
                break;
            case RESPONSE_FETCH_DONE:
                self.cacheTotolTime = self.duration;
                break;
            case RESPONSE_FETCH_ERROR:
                self._requestStreamFailed("the device is busy or file is not existed.<br/> errno is " + data.errno,6800);
                break;
            case RESPONSE_FETCH_READY:
                self.fetchWorker.postMessage({
                    type:REQUEST_FETCH_START,
                    option:{
                        url:self.url,
                        isLive:self.isLive,
                        duration:self.duration,
                        playParams:self.playParams,
                        retryTimes:self.connectionRetryTimes,
                        retryDuration:self.connectionRetryDuration
                    }
                });
                console.info("fetchWorker ready to work")
                break;
       }
    }

    console.info("new Worker - fetch.js");
}

var _onInitNewDecoder = function(data){
    if(data.ret == 0){
        console.info("_onInitNewDecoder successfully");
    }else{
        this.stop(true);
        console.info("_onInitNewDecoder failed");
    }
}

var _onUpdateVideoParam = function(data){
    this.videoWidth = data.width
    this.videoHeight = data.height;
    console.info("_onUpdateVideoParam width=%d,height=%d",data.width,data.height);
}

var _onFeedDataHasErorr = function(data){
    console.info("_onFeedDataHasErorr ,data.isAudio=",data.isAudio,"data.ret = ",data.ret);
}

var _onReceiveDecodeFrame = function(data){
   // console.info("_onReceiveDecodeFrame isVideo=",!data.isAudio);
    this._pushFrameBuffer(data);
}

var _onTerminateDecoder = function(){
    this.decodeWorker.terminate();
    this.decodeWorker = null;
}
var _onDecodeFinished = function(){
    if(this.decodeWorker !=null){
       //this.decodeWorker.terminate();
    }
    console.info("_onDecodeFinished");
}


var playLive = function(deviceID,channel,sessionKey,GPSCallback){
    if(!deviceID || !channel || !sessionKey ){
        console.error("deviceID,channel,sessionKey must not be null");
        return false;
    }
    var ignoreStopEvent = true;
    this._setStopped(ignoreStopEvent);

    this.isLive = true;
    this.isLive == true && this.playButton.hide();
    this.playerState = playerStateIdle;
    this.duration = 0;
    this.GPSCallback = GPSCallback || null;
    this._loadingUI(true);

    this.deviceID =  deviceID;
    this.sessionKey = sessionKey;
    this.channel    = channel;
    this.setCacheSeconds(this.cacheSeconds);
    this._resetMediaObjectCache();
    this.playParams = JSON.stringify(
        {
                "messageID": 701,
                "nLength": 11,
                "nQueue": 1,
                "nParameter1": 11,
                "nParameter2": 12,
                "webstring": "abcefghijk",
                "szDeviceID": deviceID,
                "nChannel": channel,//channel number started from 1
                "nStreamIndex": 0,// 0:sub stream,1 main stream
                "nSessionID": sessionKey,//Session
                "nStreamType": 1,//substream
        }
    );
    this._initDecoder();
    return true;
}


var _onPlayBtnStopEvent = function(deviceID,channelCount,isUser){
    this.IOnBtnStopEvent
    && this.IOnBtnStopEvent({deviceID:deviceID,channelCount:channelCount,isUser:isUser});
}

var playHistory = function(deviceID,channel,sessionKey,startOffset,startTime,endTime,GPSCallback){
    if(!deviceID || !channel || !sessionKey || startOffset < 0 || !startTime || !endTime){
        console.error("deviceID,channel,sessionKey,startOffset,startTime,endTime must not be null");
        return false;
    }
    var ignoreStopEvent = true;
    this._setStopped(ignoreStopEvent);

    this._loadingUI(true);
    this.playerState = playerStateIdle;
    //this.isLive = false;
    this.isLive = false;
    this.isLive == false && this.playButton.show();
    this.duration = getDuration(startTime,endTime);
    console.info("duration startTime=%s,endTime=%s,duration=%d",startTime,endTime,this.duration);
    this.GPSCallback = GPSCallback || null;

    this.deviceID =  deviceID;
    this.sessionKey = sessionKey;
    this.channel    = channel;
    this.startTime  =   startTime;
    this.endTime    = endTime;
    this.startOffset  = startOffset;

    this.setCacheSeconds(this.cacheSeconds);
    this._resetMediaObjectCache();
    this.playParams = JSON.stringify(
        {
            "messageID": 480,
            "nLength": 11,
            "nQueue": 1,
            "nParameter1": 11,
            "nParameter2": 12,
            "webstring": "abcefghijk",
            "szDeviceID": deviceID,                        //设备ID
            "bCenter":false,                               //中心或者设备录
            "nChannel":channel,	                           //通道编号
            "nSessionID": sessionKey,	                   //Session id
            "szStartTime":startTime,	                   //开始时间
            "szStopTime":endTime,	                       //结束时间
            "nOnlyIFrame":0 ,                              //是否只要I帧
            "nRecordType":0,	                           //录像类型
            "nStartPosition":startOffset                   //开始位置
        }
    );
   this._initDecoder();
   return true;
}

var pause = function(){
    if(!this.isLive){
        this.isPlaying() && this._setPause();
    }
}

var resume = function(){
    if(!this.isLive){
        this.isPaused() && this._setPlay();
    }
}


var stop = function(ignoreStopEvent){
    this._setStopped(ignoreStopEvent);
}

var seekTo = function(seconds){
    if(this.isLive) return ;
    var startTime = getSeekTime(this.startTime,seconds);
    this.playHistory(this.deviceID,this.channel,this.sessionKey,this.startOffset,startTime,this.endTime,this.GPSCallback)
}




var fullscreen = function () {
   if (this.webglPlayer) {
       this.webglPlayer.fullscreen();
   }
};

/**How many milliseconds does the frameBuffer keep */
var _getDistanceOfFrameBuffer = function(frameBuffer,limit){
    const size = frameBuffer.length;
    if (0 == size) {
        return false;
    }
    var oldest = frameBuffer[0];
    if(oldest.index == VideoFrameLast)return true;
    var newest = frameBuffer[size - 1];
    if(newest.index == VideoFrameLast)return true;
    return size >= limit;
}

var _requestDecoding = function(){
    var self = this;
    if(this._getDistanceOfFrameBuffer(this.videoFrameBuffer,1)  ||
       this._getDistanceOfFrameBuffer(this.audioFrameBuffer,1)
    ){
        //this.isIdle() && this._setPlay();
       if(this.isPaused()) return;
       this.isIdle() && this._setPlay();
    }

    while(true){
        const frame = self._popCacheQueue();
        if(frame != null){
            var postData = {
                type:REQUEST_FEED_DECODER_DATA,
                data:frame.data,
                pts:frame.pts,
                index:frame.index,
                isAudio:frame.isAudio
            };
            this.decodeWorker.postMessage(postData,[postData.data]);
        }else{
            if(!this.isLive){
                this.fetchWorker && this.fetchWorker.postMessage({
                    type:REQUEST_FETCH_FRAMES
                });
            }
            break;
        }
    }
    
    /*
    while(this.localQueue.length >0){
       var obj =  this.localQueue[0];
       //console.info("_requestDecoding2",obj.pts,this.loopTotalTime + 2000)
       if(obj.pts < this.loopTotalTime + 2000){
            this.localQueue.shift();
            var postData = {
                type:REQUEST_FEED_DECODER_DATA,
                data:obj.data,
                pts:obj.pts,
                index:obj.index,
                isAudio:obj.isAudio
            };
            this.decodeWorker.postMessage(postData,[postData.data]);
       }else{
            break;
       }
    };
    if(this.localQueue < 60){
        this.fetchWorker.postMessage({
            type:REQUEST_FETCH_FRAMES
        });
    }*/
}

var isPaused = function(){
    return this.playerState == playerStatePausing;
 }

 var isPlaying = function(){
     return this.playerState == playerStatePlaying;
 }

 var isStopped = function(){
     return this.playerState == playerStateStopped;
 }

 var isIdle = function(){
     return this.playerState == playerStateIdle;
 }

 var _setPause = function(){
    if(this.isPaused()) return ;
    this.playerState = playerStatePausing;
    this._loadPlayPauseUI(false);
    //change UI to stopped
 }
 var _setPlay = function(){
    if(this.isPlaying()) return ;
    this.playerState = playerStatePlaying;
    this._loopRender();
    this._loadPlayPauseUI(true);
    //change UI to play
 }
 var _setIdle = function(){
    if(this.isIdle()) return ;
    this.playerState = playerStateIdle;

    //change UI to waiting

 }
 var _setStopped = function(ignoreStopEvent){
    if(this.isStopped()) return ;
    byteBuffer = null;
    this.playerState = playerStateStopped;

    /**send message to fetch worker delete cache files */
    if(this.fetchWorker != null){
        this.fetchWorker.terminate();
        this.fetchWorker = null;
    }

    //stop decoding ,when receive callback terminate the decodeWorker
    if(this.decodeWorker != null){
        this.decodeWorker.terminate();
        this.decodeWorker = null;
    }

    this._stopTracker();

    this.loopRenderTask && cancelAnimationFrame(this.loopRenderTask);
    this.loopRenderTask     = null;
     //playParams
     var deviceID = this.deviceID;
     var channelCount = this.channel;
     this.deviceID           = null;
     this.channel            = null;
     this.sessionKey         = null;
     this.startOffset        = null;
     this.startTime          = null;
     this.endTime            = null;
     this.GPSCallback        = null;

     this.isEncryptedStream  = false;
     this.passwordMd5        = "";
     this.onRequestPassword  = null;
    //stop player
    this.isLive             = false;
    this.localFile          = null; // is not live ,save raw frames in MEMFS
    this.localQueue         = []; // is live ,save raw frames in QUEUE
    this.readOffset         = 0;
    this.writeOffset        = 0;

    this.duration           = 0;
    /*** audio config ***/
    this.audioEncoding      = "16bitInt";
    this.audioChannels      = 1;
    this.audioSampleRate    = 8000;
    this.audioType          = 1; //1:G711a 2:AAC
    /*** video config ***/
    this.videoWidth         = 0;
    this.videoheight        = 0;
    this.videoFrameRate     = 0;
    this.videoType          = 1; //1:H264 2:H265

    this.videoFrameBuffer   = [];
    this.audioFrameBuffer   = [];
    this.GpsFrameBuffer     = [];

    this.loopNowTime        = 0;
    this.loopLastTime       = 0;
    this.loopTotalTime      = 0;

    this.cacheQueuePlayState   = 0;
    this.cacheMillisToStarted  = 0;
    this.cacheMillisToPlaying  = 0;
    this.cacheQueue             = [];

    this.lastAudioPtsAdjust   = -1;
    this.lastVideoPtsAdjust   = -1;
    this.baseAudioPtsAdjust   = -1;
    this.baseVideoPtsAdjust   = -1;
    //how many pts has been cache for playHistory()
    this.cacheTotolTime     = 0;
    if(this.pcmPlayer!=null){
        this.pcmPlayer.destroy();
        this.pcmPlayer = null;
    }
    this.webglPlayer && this.webglPlayer.restore();
    this.webglPlayer = null;

    this.isFirstVideoFrame  = true;
    this.isFirstAudioFrame  = true;

    this.beginTimeOffset    = -1;
    this._hideOverlay();

    if(this.timeTrack) this.timeTrack.width(0);
    if(this.timeCacheTrack) this.timeCacheTrack.width(0);
    if(this.durationLabel) this.durationLabel.html("00:00:00");
    if(this.timeLabel) this.timeLabel.html("00:00:00");
    this._setSpeedRate("");
    //this.url = "";
    //change UI to stopped
    this._onPlayBtnStopEvent(deviceID,channelCount,!ignoreStopEvent);

 }

 var release = function(){
    var ignoreStopEvent = true;
    this._setStopped(ignoreStopEvent);
    this.GPSCallback        = null;
    this.IOnBtnStopEvent   = null;
    console.info("player released")
 }

 var _setVolume = function(gainValue){
    if(gainValue >= 0 && gainValue <= 1.0){
        this.gainValue = gainValue;
        this.pcmPlayer != null && this.pcmPlayer.setVolume(gainValue);
        if(gainValue == 0){
            this._setVolumeUI(true);
        }else{
            this._setVolumeUI(false);
        }
    }
 }

 var _setVolumeUI = function(isMuted){
   var rootElement = $(this.container);
   var volumeButton = rootElement.find(".mejs-volume-button");
   if(isMuted){
      volumeButton.removeClass("mejs-mute").addClass("mejs-unmute");
   }else{
      volumeButton.removeClass("mejs-unmute").addClass("mejs-mute");
   }

 }


/**
 *
 * @param {isAudio:true,data:data,pts:pts,index:(0||-1||1)} obj
 * @field isAudio refers to very obvious
 * @field data refers to raw data like PCM or YUV420
 * @field pts refers to when to render
 * @field index has generally 4 four (-1||0||1) -1:end frame(can not be rendered), 0: first frame ,1:middle frame
 * @desc [first frame and last frame] must be video frame ,so isAudio value of the frame is false
 */
var downloadOnce = false;
var byteBuffer = null;
var testFile = function(data){
    if(byteBuffer == null){
        byteBuffer = data.slice(0);
      }else{
          var tmp = new Uint8Array(byteBuffer.byteLength + data.byteLength);
          tmp.set(new Uint8Array(byteBuffer), 0);
          tmp.set(new Uint8Array(data.slice(0)), byteBuffer.byteLength);
          byteBuffer = tmp.buffer
    }
    if(!downloadOnce && byteBuffer.byteLength >=  200 * 1024){
        console.error("download........")
        downloadFile(byteBuffer,"video/mp4", "blank.aac")
        downloadOnce = true;

   }
}

var _pushFrameBuffer = function(obj){    
   /*if(this.isIdle()){
            this.playerState = playerStatePlaying;
            this._hideOverlay();
            this._loadPlayPauseUI();
   }*/
   //this.loopTotalTime = 0;
   //Shows the elapsed time  of live stream,Unfortunately pts time is not right sometime
   //this.loopTotalTime = obj.pts;
   if(obj.isAudio){
       this.audioFrameBuffer.push(obj);
   }else{
       this.videoFrameBuffer.push(obj);
   }
}

var _loopRender = function(){
    if(this.isStopped()) return ;
    this.loopNowTime = Date.now();
    const pastTime = this.loopLastTime > 0 ? this.loopNowTime - this.loopLastTime : 0;
    this.loopLastTime = this.loopNowTime;

    var self = this;
    this.loopRenderTask = requestAnimationFrame(_loopRender.bind(this))
    if(!this.isPlaying()) {
        console.info("the player state is not playing,so have to stop loopRender");
        return ;
    }
    const audioFrameLength = this.audioFrameBuffer.length;
    const videoFrameLength = this.videoFrameBuffer.length;
    if(!this.isLive && this.loopTotalTime >= this.duration){
        this._setStopped(true);
        return;
    }
    if(this.GpsFrameBuffer.length > 0){
        var gpsObj = this.GpsFrameBuffer[0];
        if(gpsObj.pts <= this.loopTotalTime){
            this.GpsFrameBuffer.shift();
            console.info("gps callback1",gpsObj,self.GPSCallback);
            self.GPSCallback && setTimeout(function(){
                self.GPSCallback(gpsObj);
            },20);
        }
    }
    if(audioFrameLength + videoFrameLength > 0 ){
        //caculate duration of every requestAnimatedFrame
        var frame = null;
        this.loopTotalTime += pastTime;
        var millis = 1200;
        if(millis < this.cacheMillisToPlaying) 
           millis = this.cacheMillisToPlaying;
        if(videoFrameLength > 0){
            while(this.videoFrameBuffer.length > 0){
                frame = this.videoFrameBuffer[0];
                if(this.loopTotalTime - frame.pts > millis){
                    this.videoFrameBuffer.shift();
                    frame = null;
                }else{
                    break;
                }
            }
            if(frame != null && frame.index == -1){
                this._setStopped(true);
                console.info("last frames");
                return ;
            }
            if(frame != null && frame.index == VideoFrameFirst || frame.pts <= this.loopTotalTime){
                console.info("_loopRender","renderVideo,pts=",frame.pts,"loopTotalTime=",this.loopTotalTime,"size="+this.videoFrameBuffer.length);
                this.videoFrameBuffer.shift();
                const diff = this.loopTotalTime - frame.pts;
                if(diff > 800){
                    //ignore
                    console.info("diff("+diff+") > 800,ignore videoFrame,FrameLength=",this.videoFrameBuffer.length);
                }else {
                    this._renderVideoFrame(frame);        
                }
                //console.info("9999 renderVideoFrame ,pts=%d,loopTotalTime=%d,bufferSize=%d",frame.pts,this.loopTotalTime,this.videoFrameBuffer.length);
            }
        }
        if(audioFrameLength > 0){
            while(this.audioFrameBuffer.length > 0){
                frame = this.audioFrameBuffer[0];
                if(this.loopTotalTime - frame.pts > millis){
                    this.audioFrameBuffer.shift();
                    frame = null;
                }else{
                    break;
                }
            }
            if(frame != null && frame.pts <= this.loopTotalTime){
                console.info("_loopRender","renderAudio,pts=",frame.pts,"loopTotalTime=",this.loopTotalTime,"size="+this.videoFrameBuffer.length)
                this.audioFrameBuffer.shift();
                this._renderAudioFrame(frame);
                //console.info("9999 renderAudioFrame ,pts=%d,loopTotalTime=%d,bufferSize=%d",frame.pts,this.loopTotalTime,this.videoFrameBuffer.length);
            }
        }
    }
}

var _setOnRequestPasswordListener = function(cb){
    this.onRequestPassword = cb;
}

var _setEncryptedPassword = function(password){
    if(this.isEncryptedStream){
        if(this.passwordMd5.toUpperCase() == md5(password).toUpperCase()){
            this.fetchWorker.postMessage({
                type:REQUEST_FETCH_PASSWORD,
            });
            console.info("password is correct");
            return true;
        }else{
            console.info("password is not incorrect");
            return false;
        }
    }
    return true;
}

var methods = {
    /*****external api*****/
    playLive:playLive,
    playHistory:playHistory,
    pause:pause,
    resume:resume,
    stop :stop,
    seekTo:seekTo,
    isPaused:isPaused,
    isPlaying:isPlaying,
    isStopped:isStopped,
    fullscreen:fullscreen,
    isIdle:isIdle,
    isPaused:isPaused,
    isStopped:isStopped,
    setOnRequestPasswordListener:_setOnRequestPasswordListener,
    setEncryptedPassword:_setEncryptedPassword,
    setVolume:_setVolume,
    screenshot:_screenshot,
    resize:_resizeUI,
    release:release,
    getCacheSeconds:_getCacheSeconds,
    setCacheSeconds:_setCacheSeconds,
    /******end external api******/
    /*****internal api******/
    _getDistanceOfFrameBuffer,_getDistanceOfFrameBuffer,
    _loopRender,_loopRender,
    _pushFrameBuffer:_pushFrameBuffer,
    _adjustLoopTotalTime:_adjustLoopTotalTime,
    _setIdle:_setIdle,
    _setPause:_setPause,
    _setPlay:_setPlay,
    _setStopped:_setStopped,
    _addCacheQueue:_addCacheQueue,
    _popCacheQueue:_popCacheQueue,
    _resetMediaObjectCache:_resetMediaObjectCache,
    _requestDecoding:_requestDecoding,
    _onInitNewDecoder:_onInitNewDecoder,
    _onUpdateVideoParam:_onUpdateVideoParam,
    _onReceiveDecodeFrame:_onReceiveDecodeFrame,
    _onDecodeFinished:_onDecodeFinished,
    _onTerminateDecoder:_onTerminateDecoder,
    _setSpeedRate:_setSpeedRate,
    _startTracker:_startTracker,
    _updateTracker:_updateTracker,
    _stopTracker:_stopTracker,
    _initPlayer:_initPlayer,
    _initDecoder:_initDecoder,
    _initFetch:_initFetch,
    _onFeedDataHasErorr:_onFeedDataHasErorr,
    _renderAudioFrame:_renderAudioFrame,
    _renderVideoFrame:_renderVideoFrame,
    _loadingUI:_loadingUI,
    _loadErrorUI:_loadErrorUI,
    _loadingInfoUI:_loadingInfoUI,
    _loadPlayPauseUI:_loadPlayPauseUI,
    _setVolumeUI:_setVolumeUI,
    _hideOverlay:_hideOverlay,
    _requestStreamFailed:_requestStreamFailed,
    _onPlayBtnStopEvent:_onPlayBtnStopEvent,
    /****end internal api *****/
}

var __fn__ = Player.prototype

for (let method in methods) {
    __fn__[method] = methods[method];
}



/***********************START Helper **************************/
var getDuration = function(startTime,endTime){
    // Convert to Date objects
    const startDate = new Date(startTime);
    const endDate = new Date(endTime);
    // Calculate the time difference in milliseconds
    return Math.abs(endDate - startDate);
}

var formatTime = function (s) {
    s /= 1000;
    var h = Math.floor(s / 3600) < 10 ? '0' + Math.floor(s / 3600) : Math.floor(s / 3600);
    var m = Math.floor((s / 60 % 60)) < 10 ? '0' + Math.floor((s / 60 % 60)) : Math.floor((s / 60 % 60));
    var s = Math.floor((s % 60)) < 10 ? '0' + Math.floor((s % 60)) : Math.floor((s % 60));
    return result = h + ":" + m + ":" + s;
};

var getSeekTime = function(startTimeStr,seconds){
    const date0 = new Date(startTimeStr);
    date0.setSeconds(date0.getSeconds() + seconds);
    return date0.format("yyyy-MM-dd hh:mm:ss");
}

Date.prototype.format = function(fmt)
{
    var o = {
        "M+" : this.getMonth()+1,
        "d+" : this.getDate(),
        "h+" : this.getHours(),
        "m+" : this.getMinutes(),
        "s+" : this.getSeconds(),
        "q+" : Math.floor((this.getMonth()+3)/3),
        "S"  : this.getMilliseconds()
    };
    if(/(y+)/.test(fmt))
        fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
    for(var k in o)
        if(new RegExp("("+ k +")").test(fmt))
    fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
return fmt;
}


var downloadFile = function(data,type,filename){
    var blob;
    console.info("window",window);
    if(typeof window.Blob == "function"){
        blob = new Blob([data],{type:type});
    }else{
        var BlobBuilder = window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder || window.MSBlobBuilder;
        var blobBuilder = new BlobBuilder();
        blobBuilder.append(data);
        blob = blobBuilder.getBlob(type);
    }

    var URL = window.URL || window.webkitURL;
    var url = URL.createObjectURL(blob);
    var anchor = document.createElement("a");
    if('download' in anchor){
        anchor.style.visibility = "hidden";
        anchor.href = url;
        anchor.download = filename;
        document.body.appendChild(anchor);
        var evt = document.createEvent("MouseEvents");
        evt.initEvent("click",true,true);
        anchor.dispatchEvent(evt);
        document.body.removeChild(anchor);
    }else if(navigator.MSBlobBuilder){
         navigator.msSaveBlob(blob,filename);
    }else{
        location.href = url;
    }
}
function getJpegName() {
    const now = new Date();
  
    const pad = (n, width = 2) => String(n).padStart(width, '0');
  
    const year = now.getFullYear();
    const month = pad(now.getMonth() + 1);
    const day = pad(now.getDate());
  
    const hour = pad(now.getHours());
    const minute = pad(now.getMinutes());
    const second = pad(now.getSeconds());
    const millisecond = pad(now.getMilliseconds(), 3); // 补足 3 位
  
    return `${year}${month}${day}_${hour}${minute}${second}${millisecond}.jpeg`;
  }

/***********************END Helper **************************/
